// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using CIP4.Common;
using CIP4.Simple;

namespace CIP4.Query
{
    class CIP4QueryContextType
    {
        // Executes the expression tree that is passed to it.
        internal static object Execute<TItemType>(Expression expression, bool IsEnumerable)
        {
            StrongWrapper<TItemType> wrapper = new StrongWrapper<TItemType>(Globals.DefaultLogger);
            // The expression must represent a query over the data source.
            if (!IsQueryOverDataSource(expression))
                throw new InvalidProgramException("No query over the data source was specified.");

            // Get the place name(s) to query the Web service with.
            LocationFinder<TItemType> lf = new LocationFinder<TItemType>();
            CollectionExternalIDFromExpression(expression);
            
            if (lf.TemplateItem == null)
                throw new InvalidQueryException("bug in the program.");

            IBooleanValue selectionExpression = (IBooleanValue)lf.TemplateItem;

            // Call the Web service and get the results.
            IEnumerable<TItemType> items = wrapper.GetItems(selectionExpression, wrapper.Logger) ;

            // Copy the IEnumerable places to an IQueryable.
            IQueryable<TItemType> queryablItems = items.AsQueryable();

            // Copy the expression tree that was passed in, changing only the first
            // argument of the innermost MethodCallExpression.
            ExpressionTreeModifier treeCopier = new ExpressionTreeModifier(queryablItems);
            Expression newExpressionTree = treeCopier.CopyAndModify(expression);

            // This step creates an IQueryable that executes by replacing Queryable methods with Enumerable methods.
            if (IsEnumerable)
                return queryablItems.Provider.CreateQuery(newExpressionTree);
            
            return queryablItems.Provider.Execute(newExpressionTree);
        }

        private static void CollectionExternalIDFromExpression(Expression expression)
        {
            Expression me = expression;
            while (me is MethodCallExpression)
            {
                me = ((MethodCallExpression)me).Arguments[0];
                if (me is ConstantExpression)
                {
                    return;
                }
            }
            throw new ApplicationException(string.Format("Unable to find CollectionExternalID in {0}", expression));
        }

        private static bool IsQueryOverDataSource(Expression expression)
        {
            // If expression represents an unqueried IQueryable data source instance,
            // expression is of type ConstantExpression, not MethodCallExpression.
            return (expression is MethodCallExpression);
        }
    }
    internal class InnermostWhereFinder : ExpressionVisitor
    {
        private MethodCallExpression innermostWhereExpression;

        public MethodCallExpression GetInnermostWhere(Expression expression)
        {
            Visit(expression);
            return innermostWhereExpression;
        }

        protected override Expression VisitMethodCall(MethodCallExpression expression)
        {
            if (expression.Method.Name == "Where")
                innermostWhereExpression = expression;

            Visit(expression.Arguments[0]);

            return expression;
        }
    }
    internal class LocationFinder<CData> : ExpressionVisitor
    {
        public RulesEngine.PolishExpressionType _TemplateItem;

        public RulesEngine.PolishExpressionType TemplateItem
        {
            get
            {
                if (_TemplateItem == null)
                    _TemplateItem = new RulesEngine.PolishExpressionType();
                return _TemplateItem;
            }
            set
            {
                _TemplateItem = value;
            }
        }

        protected override Expression VisitBinary(BinaryExpression be)
        {
            if (be != null && be.NodeType == ExpressionType.Equal)
            {
                if (be.Left is MemberExpression && ((MemberExpression)be.Left).Expression is ParameterExpression &&
                    ((MemberExpression)be.Left).Expression.Type == typeof(CData))
                {
                    if (be.Method != null && be.Method.Name == "op_Equality" && be.Left is MemberExpression)
                    {
                        string name = ((MemberExpression)be.Left).Member.Name;
                        string value = ExpressionTreeHelpers.GetValueFromEqualsExpression(be, typeof(CData), name);

                        TemplateItem = TemplateItem + 
                            new RulesEngine.StringAtomType(RulesEngine.AtomSourceEnum.namedValue, name) +
                            new RulesEngine.StringAtomType(value) +
                            RulesEngine.BooleanOperatorsEnum.EQL;
                        return be;
                    }
                }
                return base.VisitBinary(be);
            }
            
            return base.VisitBinary(be);
        }
    }
    internal class ExpressionTreeModifier : ExpressionVisitor
    {
        private readonly IQueryable queryablePlaces;

        internal ExpressionTreeModifier(IQueryable places)
        {
            queryablePlaces = places;
        }

        internal Expression CopyAndModify(Expression expression)
        {
            return Visit(expression);
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            // Replace the constant CIP4QueryType arg with the queryable Place collection.
            if (c.Value is ICIP4QueryBase)
                return Expression.Constant(queryablePlaces);
            
            return c;
        }
    }
    internal class ExpressionTreeHelpers
    {
        internal static bool IsMemberEqualsValueExpression(Expression exp, Type declaringType, string memberName)
        {
            if (exp.NodeType != ExpressionType.Equal)
                return false;

            BinaryExpression be = (BinaryExpression)exp;

            // Assert.
            if (IsSpecificMemberExpression(be.Left, declaringType, memberName) &&
                IsSpecificMemberExpression(be.Right, declaringType, memberName))
                throw new Exception("Cannot have 'member' == 'member' in an expression!");

            return (IsSpecificMemberExpression(be.Left, declaringType, memberName) ||
                IsSpecificMemberExpression(be.Right, declaringType, memberName));
        }

        internal static bool IsSpecificMemberExpression(Expression exp, Type declaringType, string memberName)
        {
            return ((exp is MemberExpression) &&
                (((MemberExpression)exp).Member.DeclaringType == declaringType) &&
                (((MemberExpression)exp).Member.Name == memberName));
        }

        internal static string GetValueFromEqualsExpression(BinaryExpression be, Type memberDeclaringType, string memberName)
        {
            if (be.NodeType != ExpressionType.Equal)
                throw new Exception("There is a bug in this program.");

            if (be.Left.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression me = (MemberExpression)be.Left;

                if (me.Member.DeclaringType == memberDeclaringType && me.Member.Name == memberName)
                {
                    return GetValueFromExpression(be.Right);
                }
            }
            else if (be.Right.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression me = (MemberExpression)be.Right;

                if (me.Member.DeclaringType == memberDeclaringType && me.Member.Name == memberName)
                {
                    return GetValueFromExpression(be.Left);
                }
            }

            // We should have returned by now.
            throw new Exception("There is a bug in this program.");
        }

        internal static string GetValueFromExpression(Expression expression)
        {
            if (expression.NodeType == ExpressionType.Constant)
                return (string)(((ConstantExpression)expression).Value);
            
            throw new InvalidQueryException(
                String.Format("The expression type {0} is not supported to obtain a value.", expression.NodeType));
        }
    }
    public class InvalidQueryException : Exception
    {
        private readonly string message;

        public InvalidQueryException(string message)
        {
            this.message = message + " ";
        }

        public override string Message
        {
            get
            {
                return "The client query is invalid: " + message;
            }
        }
    }
}
